// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/gfx/color_analysis.h"

#include <stddef.h>
#include <stdint.h>

#include <vector>

#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/image/image.h"

namespace color_utils {

const unsigned char k1x1White[] = {
    0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
    0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
    0x08, 0x02, 0x00, 0x00, 0x00, 0x90, 0x77, 0x53,
    0xde, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47,
    0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
    0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00,
    0x0b, 0x13, 0x00, 0x00, 0x0b, 0x13, 0x01, 0x00,
    0x9a, 0x9c, 0x18, 0x00, 0x00, 0x00, 0x07, 0x74,
    0x49, 0x4d, 0x45, 0x07, 0xdb, 0x02, 0x11, 0x15,
    0x16, 0x1b, 0xaa, 0x58, 0x38, 0x76, 0x00, 0x00,
    0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x43, 0x6f,
    0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x43, 0x72,
    0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69,
    0x74, 0x68, 0x20, 0x47, 0x49, 0x4d, 0x50, 0x57,
    0x81, 0x0e, 0x17, 0x00, 0x00, 0x00, 0x0c, 0x49,
    0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xff,
    0xff, 0x3f, 0x00, 0x05, 0xfe, 0x02, 0xfe, 0xdc,
    0xcc, 0x59, 0xe7, 0x00, 0x00, 0x00, 0x00, 0x49,
    0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};

const unsigned char k1x3BlueWhite[] = {
    0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
    0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03,
    0x08, 0x02, 0x00, 0x00, 0x00, 0xdd, 0xbf, 0xf2,
    0xd5, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47,
    0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
    0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00,
    0x0b, 0x13, 0x00, 0x00, 0x0b, 0x13, 0x01, 0x00,
    0x9a, 0x9c, 0x18, 0x00, 0x00, 0x00, 0x07, 0x74,
    0x49, 0x4d, 0x45, 0x07, 0xdb, 0x02, 0x12, 0x01,
    0x0a, 0x2c, 0xfd, 0x08, 0x64, 0x66, 0x00, 0x00,
    0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x43, 0x6f,
    0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x43, 0x72,
    0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69,
    0x74, 0x68, 0x20, 0x47, 0x49, 0x4d, 0x50, 0x57,
    0x81, 0x0e, 0x17, 0x00, 0x00, 0x00, 0x14, 0x49,
    0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xff,
    0xff, 0x3f, 0x13, 0x03, 0x03, 0x03, 0x03, 0x03,
    0xc3, 0x7f, 0x00, 0x1e, 0xfd, 0x03, 0xff, 0xde,
    0x72, 0x58, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x49,
    0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};

const unsigned char k1x3BlueRed[] = {
    0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
    0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
    0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03,
    0x08, 0x02, 0x00, 0x00, 0x00, 0xdd, 0xbf, 0xf2,
    0xd5, 0x00, 0x00, 0x00, 0x01, 0x73, 0x52, 0x47,
    0x42, 0x00, 0xae, 0xce, 0x1c, 0xe9, 0x00, 0x00,
    0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00,
    0x0b, 0x13, 0x00, 0x00, 0x0b, 0x13, 0x01, 0x00,
    0x9a, 0x9c, 0x18, 0x00, 0x00, 0x00, 0x07, 0x74,
    0x49, 0x4d, 0x45, 0x07, 0xdb, 0x02, 0x12, 0x01,
    0x07, 0x09, 0x03, 0xa2, 0xce, 0x6c, 0x00, 0x00,
    0x00, 0x19, 0x74, 0x45, 0x58, 0x74, 0x43, 0x6f,
    0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 0x43, 0x72,
    0x65, 0x61, 0x74, 0x65, 0x64, 0x20, 0x77, 0x69,
    0x74, 0x68, 0x20, 0x47, 0x49, 0x4d, 0x50, 0x57,
    0x81, 0x0e, 0x17, 0x00, 0x00, 0x00, 0x14, 0x49,
    0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0xf8, 0xcf,
    0xc0, 0xc0, 0xc4, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
    0xf0, 0x1f, 0x00, 0x0c, 0x10, 0x02, 0x01, 0x2c,
    0x8f, 0x8b, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x49,
    0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
};

const HSL kDefaultLowerBound = { -1, -1, 0.15 };
const HSL kDefaultUpperBound = { -1, -1, 0.85 };

// Creates a 1-dimensional png of the pixel colors found in |colors|.
scoped_refptr<base::RefCountedMemory> CreateTestPNG(
    const std::vector<SkColor>& colors)
{
    SkBitmap bitmap;
    bitmap.allocN32Pixels(colors.size(), 1);

    SkAutoLockPixels lock(bitmap);
    for (size_t i = 0; i < colors.size(); ++i) {
        bitmap.eraseArea(SkIRect::MakeXYWH(i, 0, 1, 1), colors[i]);
    }
    return gfx::Image::CreateFrom1xBitmap(bitmap).As1xPNGBytes();
}

class MockKMeanImageSampler : public KMeanImageSampler {
public:
    MockKMeanImageSampler()
        : current_result_index_(0)
    {
    }

    explicit MockKMeanImageSampler(const std::vector<int>& samples)
        : prebaked_sample_results_(samples)
        , current_result_index_(0)
    {
    }

    ~MockKMeanImageSampler() override { }

    void AddSample(int sample)
    {
        prebaked_sample_results_.push_back(sample);
    }

    int GetSample(int width, int height) override
    {
        if (current_result_index_ >= prebaked_sample_results_.size()) {
            current_result_index_ = 0;
        }

        if (prebaked_sample_results_.empty()) {
            return 0;
        }

        return prebaked_sample_results_[current_result_index_++];
    }

protected:
    std::vector<int> prebaked_sample_results_;
    size_t current_result_index_;
};

// Return true if a color channel is approximately equal to an expected value.
bool ChannelApproximatelyEqual(int expected, uint8_t channel)
{
    return (abs(expected - static_cast<int>(channel)) <= 1);
}

// Compute minimal and maximal graylevel (or alphalevel) of the input |bitmap|.
// |bitmap| has to be allocated and configured to kA8_Config.
void Calculate8bitBitmapMinMax(const SkBitmap& bitmap,
    uint8_t* min_gl,
    uint8_t* max_gl)
{
    SkAutoLockPixels bitmap_lock(bitmap);
    DCHECK(bitmap.getPixels());
    DCHECK_EQ(bitmap.colorType(), kAlpha_8_SkColorType);
    DCHECK(min_gl);
    DCHECK(max_gl);
    *min_gl = std::numeric_limits<uint8_t>::max();
    *max_gl = std::numeric_limits<uint8_t>::min();
    for (int y = 0; y < bitmap.height(); ++y) {
        uint8_t* current_color = bitmap.getAddr8(0, y);
        for (int x = 0; x < bitmap.width(); ++x, ++current_color) {
            *min_gl = std::min(*min_gl, *current_color);
            *max_gl = std::max(*max_gl, *current_color);
        }
    }
}

class ColorAnalysisTest : public testing::Test {
};

TEST_F(ColorAnalysisTest, CalculatePNGKMeanAllWhite)
{
    MockKMeanImageSampler test_sampler;
    test_sampler.AddSample(0);

    scoped_refptr<base::RefCountedBytes> png(
        new base::RefCountedBytes(
            std::vector<unsigned char>(
                k1x1White,
                k1x1White + sizeof(k1x1White) / sizeof(unsigned char))));

    SkColor color = CalculateKMeanColorOfPNG(
        png, kDefaultLowerBound, kDefaultUpperBound, &test_sampler);

    EXPECT_EQ(color, SK_ColorWHITE);
}

TEST_F(ColorAnalysisTest, CalculatePNGKMeanIgnoreWhiteLightness)
{
    MockKMeanImageSampler test_sampler;
    test_sampler.AddSample(0);
    test_sampler.AddSample(1);
    test_sampler.AddSample(2);

    scoped_refptr<base::RefCountedBytes> png(
        new base::RefCountedBytes(
            std::vector<unsigned char>(
                k1x3BlueWhite,
                k1x3BlueWhite + sizeof(k1x3BlueWhite) / sizeof(unsigned char))));

    SkColor color = CalculateKMeanColorOfPNG(
        png, kDefaultLowerBound, kDefaultUpperBound, &test_sampler);

    EXPECT_EQ(SkColorSetARGB(0xFF, 0x00, 0x00, 0xFF), color);
}

TEST_F(ColorAnalysisTest, CalculatePNGKMeanPickMostCommon)
{
    MockKMeanImageSampler test_sampler;
    test_sampler.AddSample(0);
    test_sampler.AddSample(1);
    test_sampler.AddSample(2);

    scoped_refptr<base::RefCountedBytes> png(
        new base::RefCountedBytes(
            std::vector<unsigned char>(
                k1x3BlueRed,
                k1x3BlueRed + sizeof(k1x3BlueRed) / sizeof(unsigned char))));

    SkColor color = CalculateKMeanColorOfPNG(
        png, kDefaultLowerBound, kDefaultUpperBound, &test_sampler);

    EXPECT_EQ(SkColorSetARGB(0xFF, 0xFF, 0x00, 0x00), color);
}

TEST_F(ColorAnalysisTest, CalculatePNGKMeanIgnoreRedHue)
{
    MockKMeanImageSampler test_sampler;
    test_sampler.AddSample(0);
    test_sampler.AddSample(1);
    test_sampler.AddSample(2);

    std::vector<SkColor> colors(4, SK_ColorRED);
    colors[1] = SK_ColorBLUE;

    scoped_refptr<base::RefCountedMemory> png = CreateTestPNG(colors);

    HSL lower = { 0.2, -1, 0.15 };
    HSL upper = { 0.8, -1, 0.85 };
    SkColor color = CalculateKMeanColorOfPNG(
        png, lower, upper, &test_sampler);

    EXPECT_EQ(SK_ColorBLUE, color);
}

TEST_F(ColorAnalysisTest, CalculatePNGKMeanIgnoreGreySaturation)
{
    MockKMeanImageSampler test_sampler;
    test_sampler.AddSample(0);
    test_sampler.AddSample(1);
    test_sampler.AddSample(2);

    std::vector<SkColor> colors(4, SK_ColorGRAY);
    colors[1] = SK_ColorBLUE;

    scoped_refptr<base::RefCountedMemory> png = CreateTestPNG(colors);
    HSL lower = { -1, 0.3, -1 };
    HSL upper = { -1, 1, -1 };
    SkColor color = CalculateKMeanColorOfPNG(
        png, lower, upper, &test_sampler);

    EXPECT_EQ(SK_ColorBLUE, color);
}

TEST_F(ColorAnalysisTest, GridSampler)
{
    GridSampler sampler;
    const int kWidth = 16;
    const int kHeight = 16;
    // Sample starts at 1,1.
    EXPECT_EQ(1 + 1 * kWidth, sampler.GetSample(kWidth, kHeight));
    EXPECT_EQ(1 + 4 * kWidth, sampler.GetSample(kWidth, kHeight));
    EXPECT_EQ(1 + 7 * kWidth, sampler.GetSample(kWidth, kHeight));
    EXPECT_EQ(1 + 10 * kWidth, sampler.GetSample(kWidth, kHeight));
    // Step over by 3.
    EXPECT_EQ(4 + 1 * kWidth, sampler.GetSample(kWidth, kHeight));
    EXPECT_EQ(4 + 4 * kWidth, sampler.GetSample(kWidth, kHeight));
    EXPECT_EQ(4 + 7 * kWidth, sampler.GetSample(kWidth, kHeight));
    EXPECT_EQ(4 + 10 * kWidth, sampler.GetSample(kWidth, kHeight));
}

TEST_F(ColorAnalysisTest, FindClosestColor)
{
    // Empty image returns input color.
    SkColor color = FindClosestColor(NULL, 0, 0, SK_ColorRED);
    EXPECT_EQ(SK_ColorRED, color);

    // Single color image returns that color.
    SkBitmap bitmap;
    bitmap.allocN32Pixels(16, 16);
    bitmap.eraseColor(SK_ColorWHITE);
    color = FindClosestColor(static_cast<uint8_t*>(bitmap.getPixels()),
        bitmap.width(),
        bitmap.height(),
        SK_ColorRED);
    EXPECT_EQ(SK_ColorWHITE, color);

    // Write a black pixel into the image. A dark grey input pixel should match
    // the black one in the image.
    uint32_t* pixel = bitmap.getAddr32(0, 0);
    *pixel = SK_ColorBLACK;
    color = FindClosestColor(static_cast<uint8_t*>(bitmap.getPixels()),
        bitmap.width(),
        bitmap.height(),
        SK_ColorDKGRAY);
    EXPECT_EQ(SK_ColorBLACK, color);
}

TEST_F(ColorAnalysisTest, CalculateKMeanColorOfBitmap)
{
    // Create a 16x16 bitmap to represent a favicon.
    SkBitmap bitmap;
    bitmap.allocN32Pixels(16, 16);
    bitmap.eraseARGB(255, 100, 150, 200);

    SkColor color = CalculateKMeanColorOfBitmap(bitmap);
    EXPECT_EQ(255u, SkColorGetA(color));
    // Color values are not exactly equal due to reversal of premultiplied alpha.
    EXPECT_TRUE(ChannelApproximatelyEqual(100, SkColorGetR(color)));
    EXPECT_TRUE(ChannelApproximatelyEqual(150, SkColorGetG(color)));
    EXPECT_TRUE(ChannelApproximatelyEqual(200, SkColorGetB(color)));

    // Test a bitmap with an alpha channel.
    bitmap.eraseARGB(128, 100, 150, 200);
    color = CalculateKMeanColorOfBitmap(bitmap);

    // Alpha channel should be ignored for dominant color calculation.
    EXPECT_EQ(255u, SkColorGetA(color));
    EXPECT_TRUE(ChannelApproximatelyEqual(100, SkColorGetR(color)));
    EXPECT_TRUE(ChannelApproximatelyEqual(150, SkColorGetG(color)));
    EXPECT_TRUE(ChannelApproximatelyEqual(200, SkColorGetB(color)));
}

TEST_F(ColorAnalysisTest, ComputeColorCovarianceTrivial)
{
    SkBitmap bitmap;
    bitmap.setInfo(SkImageInfo::MakeN32Premul(100, 200));

    EXPECT_EQ(gfx::Matrix3F::Zeros(), ComputeColorCovariance(bitmap));
    bitmap.allocPixels();
    bitmap.eraseARGB(255, 50, 150, 200);
    gfx::Matrix3F covariance = ComputeColorCovariance(bitmap);
    // The answer should be all zeros.
    EXPECT_TRUE(covariance == gfx::Matrix3F::Zeros());
}

TEST_F(ColorAnalysisTest, ComputeColorCovarianceWithCanvas)
{
    gfx::Canvas canvas(gfx::Size(250, 200), 1.0f, true);
    // The image consists of vertical stripes, with color bands set to 100
    // in overlapping stripes 150 pixels wide.
    canvas.FillRect(gfx::Rect(0, 0, 50, 200), SkColorSetRGB(100, 0, 0));
    canvas.FillRect(gfx::Rect(50, 0, 50, 200), SkColorSetRGB(100, 100, 0));
    canvas.FillRect(gfx::Rect(100, 0, 50, 200), SkColorSetRGB(100, 100, 100));
    canvas.FillRect(gfx::Rect(150, 0, 50, 200), SkColorSetRGB(0, 100, 100));
    canvas.FillRect(gfx::Rect(200, 0, 50, 200), SkColorSetRGB(0, 0, 100));

    SkBitmap bitmap = skia::ReadPixels(canvas.sk_canvas());
    gfx::Matrix3F covariance = ComputeColorCovariance(bitmap);

    gfx::Matrix3F expected_covariance = gfx::Matrix3F::Zeros();
    expected_covariance.set(2400, 400, -1600,
        400, 2400, 400,
        -1600, 400, 2400);
    EXPECT_EQ(expected_covariance, covariance);
}

TEST_F(ColorAnalysisTest, ApplyColorReductionSingleColor)
{
    // The test runs color reduction on a single-colot image, where results are
    // bound to be uninteresting. This is an important edge case, though.
    SkBitmap source, result;
    source.allocN32Pixels(300, 200);
    result.allocPixels(SkImageInfo::MakeA8(300, 200));

    source.eraseARGB(255, 50, 150, 200);

    gfx::Vector3dF transform(1.0f, .5f, 0.1f);
    // This transform, if not scaled, should result in GL=145.
    EXPECT_TRUE(ApplyColorReduction(source, transform, false, &result));

    uint8_t min_gl = 0;
    uint8_t max_gl = 0;
    Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
    EXPECT_EQ(145, min_gl);
    EXPECT_EQ(145, max_gl);

    // Now scan requesting rescale. Expect all 0.
    EXPECT_TRUE(ApplyColorReduction(source, transform, true, &result));
    Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
    EXPECT_EQ(0, min_gl);
    EXPECT_EQ(0, max_gl);

    // Test cliping to upper limit.
    transform.set_z(1.1f);
    EXPECT_TRUE(ApplyColorReduction(source, transform, false, &result));
    Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
    EXPECT_EQ(0xFF, min_gl);
    EXPECT_EQ(0xFF, max_gl);

    // Test cliping to upper limit.
    transform.Scale(-1.0f);
    EXPECT_TRUE(ApplyColorReduction(source, transform, false, &result));
    Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
    EXPECT_EQ(0x0, min_gl);
    EXPECT_EQ(0x0, max_gl);
}

TEST_F(ColorAnalysisTest, ApplyColorReductionBlackAndWhite)
{
    // Check with images with multiple colors. This is really different only when
    // the result is scaled.
    gfx::Canvas canvas(gfx::Size(300, 200), 1.0f, true);

    // The image consists of vertical non-overlapping stripes 150 pixels wide.
    canvas.FillRect(gfx::Rect(0, 0, 150, 200), SkColorSetRGB(0, 0, 0));
    canvas.FillRect(gfx::Rect(150, 0, 150, 200), SkColorSetRGB(255, 255, 255));
    SkBitmap source = skia::ReadPixels(canvas.sk_canvas());
    SkBitmap result;
    result.allocPixels(SkImageInfo::MakeA8(300, 200));

    gfx::Vector3dF transform(1.0f, 0.5f, 0.1f);
    EXPECT_TRUE(ApplyColorReduction(source, transform, true, &result));
    uint8_t min_gl = 0;
    uint8_t max_gl = 0;
    Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);

    EXPECT_EQ(0, min_gl);
    EXPECT_EQ(255, max_gl);
    EXPECT_EQ(min_gl, SkColorGetA(result.getColor(0, 0)));
    EXPECT_EQ(max_gl, SkColorGetA(result.getColor(299, 199)));

    // Reverse test.
    transform.Scale(-1.0f);
    EXPECT_TRUE(ApplyColorReduction(source, transform, true, &result));
    min_gl = 0;
    max_gl = 0;
    Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);

    EXPECT_EQ(0, min_gl);
    EXPECT_EQ(255, max_gl);
    EXPECT_EQ(max_gl, SkColorGetA(result.getColor(0, 0)));
    EXPECT_EQ(min_gl, SkColorGetA(result.getColor(299, 199)));
}

TEST_F(ColorAnalysisTest, ApplyColorReductionMultiColor)
{
    // Check with images with multiple colors. This is really different only when
    // the result is scaled.
    gfx::Canvas canvas(gfx::Size(300, 200), 1.0f, true);

    // The image consists of vertical non-overlapping stripes 100 pixels wide.
    canvas.FillRect(gfx::Rect(0, 0, 100, 200), SkColorSetRGB(100, 0, 0));
    canvas.FillRect(gfx::Rect(100, 0, 100, 200), SkColorSetRGB(0, 255, 0));
    canvas.FillRect(gfx::Rect(200, 0, 100, 200), SkColorSetRGB(0, 0, 128));
    SkBitmap source = skia::ReadPixels(canvas.sk_canvas());
    SkBitmap result;
    result.allocPixels(SkImageInfo::MakeA8(300, 200));

    gfx::Vector3dF transform(1.0f, 0.5f, 0.1f);
    EXPECT_TRUE(ApplyColorReduction(source, transform, false, &result));
    uint8_t min_gl = 0;
    uint8_t max_gl = 0;
    Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
    EXPECT_EQ(12, min_gl);
    EXPECT_EQ(127, max_gl);
    EXPECT_EQ(min_gl, SkColorGetA(result.getColor(299, 199)));
    EXPECT_EQ(max_gl, SkColorGetA(result.getColor(150, 0)));
    EXPECT_EQ(100U, SkColorGetA(result.getColor(0, 0)));

    EXPECT_TRUE(ApplyColorReduction(source, transform, true, &result));
    Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);
    EXPECT_EQ(0, min_gl);
    EXPECT_EQ(255, max_gl);
    EXPECT_EQ(min_gl, SkColorGetA(result.getColor(299, 199)));
    EXPECT_EQ(max_gl, SkColorGetA(result.getColor(150, 0)));
    EXPECT_EQ(193U, SkColorGetA(result.getColor(0, 0)));
}

TEST_F(ColorAnalysisTest, ComputePrincipalComponentImageNotComputable)
{
    SkBitmap source, result;
    source.allocN32Pixels(300, 200);
    result.allocPixels(SkImageInfo::MakeA8(300, 200));

    source.eraseARGB(255, 50, 150, 200);

    // This computation should fail since all colors always vary together.
    EXPECT_FALSE(ComputePrincipalComponentImage(source, &result));
}

TEST_F(ColorAnalysisTest, ComputePrincipalComponentImage)
{
    gfx::Canvas canvas(gfx::Size(300, 200), 1.0f, true);

    // The image consists of vertical non-overlapping stripes 100 pixels wide.
    canvas.FillRect(gfx::Rect(0, 0, 100, 200), SkColorSetRGB(10, 10, 10));
    canvas.FillRect(gfx::Rect(100, 0, 100, 200), SkColorSetRGB(100, 100, 100));
    canvas.FillRect(gfx::Rect(200, 0, 100, 200), SkColorSetRGB(255, 255, 255));
    SkBitmap source = skia::ReadPixels(canvas.sk_canvas());
    SkBitmap result;
    result.allocPixels(SkImageInfo::MakeA8(300, 200));

    // This computation should fail since all colors always vary together.
    EXPECT_TRUE(ComputePrincipalComponentImage(source, &result));

    uint8_t min_gl = 0;
    uint8_t max_gl = 0;
    Calculate8bitBitmapMinMax(result, &min_gl, &max_gl);

    EXPECT_EQ(0, min_gl);
    EXPECT_EQ(255, max_gl);
    EXPECT_EQ(min_gl, SkColorGetA(result.getColor(0, 0)));
    EXPECT_EQ(max_gl, SkColorGetA(result.getColor(299, 199)));
    EXPECT_EQ(93U, SkColorGetA(result.getColor(150, 0)));
}

} // namespace color_utils
